Build better forms with React hook form | Everything you need to know
In this article, we will learn how to build better and more performant forms with the React hook form.
We will learn:
- Controlled vs Uncontrolled inputs
- Why React hook form?
- Basic usage
- Form validation
- Form Context
You can also watch the crash course where I have explained everything in detail.
Controlled vs Uncontrolled inputs
A controlled input is an input whose value is controlled by React. In other words, the value of the input is stored in the state of the component and is updated via the onChange
handler.
1import React, { useState } from 'react'23function ControlledInput() {4 const [inputValue, setInputValue] = useState('')56 const handleInputChange = event => {7 setInputValue(event.target.value)8 }910 return <input type='text' value={inputValue} onChange={handleInputChange} />11}
On the other hand, an uncontrolled input is an input whose value is not controlled by React. In other words, the value of the input is stored in the DOM and is updated via the ref
attribute.
1import React, { useRef } from 'react'23function UncontrolledInput() {4 const inputRef = useRef()56 const handleButtonClick = () => {7 alert(`Input value: ${inputRef.current.value}`)8 }910 return (11 <div>12 <input type='text' ref={inputRef} />13 <button onClick={handleButtonClick}>Get Value</button>14 </div>15 )16}
React state rerender the component whenever the state changes. So, if we have a form with many inputs, then it will rerender the component whenever the user types something in the input field. This will cause performance issues. But, with uncontrolled inputs, we can avoid this issue because ref doesn't cause the component to rerender.
Why React hook form?
- Performance(because of uncontrolled inputs)
- Easy to use
- Easy to integrate with UI libraries like Material UI, Chakra UI, etc.
- Validation out of the box or integration with Yup, Joi, etc.
Basic usage
You can install the react-hook-form
package in your project. I am going to use Chakra UI for styling. You can use any UI library or your custom styles.
Registering inputs
This library works by registering inputs to the form using a hook called useForm
1import { useForm } from 'react-hook-form'23const { register } = useForm()
The register
function is used to register the input to the form. And we need to call it to all the input and spread the return object. The first argument has to be
1<Input id='name' placeholder='Name' {...register('name')} />
Submitting the form
To submit the form, we need to call the handleSubmit
function from the useForm
hook.
1const {2 handleSubmit,3 formState: { isSubmitting }, // A state for displaying loading indicator4} = useForm()56const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))78// submit handler9const onSubmit = async data => {10 await sleep(2000)11 if (data) {12 alert(JSON.stringify(data))13 } else {14 alert('There is an error')15 }16}1718const MyForm = () => {19 return (20 <form onSubmit={handleSubmit(onSubmit)}>21 {/* form inputs */}22 {/* more form inputs */}23 <Button type='submit' isLoading={isSubmitting}>24 Submit25 </Button>26 </form>27 )28}
Explanation:
- We are using the
sleep
function to simulate an API call. - The
handleSubmit
function takes a callback function as an argument. This callback function will be called when the form is submitted. - The
handleSubmit
function will pass the form data to the callback function as an argument and we will display the data.
Default values
You can add default values to the form using the defaultValues
prop of the useForm
hook.
1const { register } = useForm({2 defaultValues: {3 name: 'Jane',4 gender: 'female',5 email: 'Jane@gmail.om',6 password: '123456',7 },8})
Getting form values
You can do it in two ways. Using the watch function or using the getValues
function.
1const { watch, getValues } = useForm()23watch('name') // watch a single input4watch(['name', 'email']) // watch multiple inputs5watch() // watch all inputs67getValues('name') // watch a single input8getValues(['name', 'email']) // watch multiple inputs9getValues() // watch all inputs
Explanation:
- The
watch
function will cause a rerender of the component where it is called whenever the value of the input changes. Similar to the react state. Use theuseWatch
hook for reducing rerenders. - The
getValues
function will return the value of the input. It will not cause a rerender. You want to use this inside an event handler likeonClick
.
You can also add onChange handlers to inputs.
1const { register } = useForm()23<Input4 id='name'5 placeholder='Name'6 {...register('name', {7 onChange: e => console.log(e.target.value),8 })}9/>
Form validation
You can validate the form using the register
function as a second parameter.
1const Myform = () => {2 const { register, errors } = useForm()3 return (4 <form>5 <FormControl isInvalid={errors.name}>6 <FormLabel htmlFor='name'>Name</FormLabel>7 <Input8 id='name'9 placeholder='Name'10 {...register('name', {11 required: 'This field is required',12 minLength: {13 value: 10,14 message: 'Minimum length should be 10',15 },16 })}17 />18 <FormErrorMessage>{errors.name && errors.name.message}</FormErrorMessage>19 </FormControl>20 <FormControl isInvalid={errors.gender}>21 <FormLabel htmlFor='gender'>Gender</FormLabel>22 <Select23 placeholder='Gender'24 {...register('gender', { required: 'This field is required' })}25 >26 <option value='male'>Male</option>27 <option value='female'>Female</option>28 </Select>29 <FormErrorMessage>30 {errors.gender && errors.gender.message}31 </FormErrorMessage>32 </FormControl>33 </form>34 )35}
Explanation:
- The
errors
object will contain all the errors of the form. You can use it to display the error message. - The
isInvalid
prop of theFormControl
component will display the error message if the input is invalid. Only needed if you use Chakra UI.
Learn more about validation from here.
Form State
You can get the form state using the formState
object coming from useform
.
1const { formState } = useForm()
Some of the useful Properties:
isDirty
isSubmitSuccessful
isSubmitting
isValid
errors
dirtyFields
Learn more about form state from here.
Form Context
You can create a form context which is a global state using the useFormContext
hook.
This can be useful when you have nested forms or you are trying to build multi-step forms.
- Wrapper component
1const App = () => {2 const formMethods = useForm({3 defaultValues: {4 companyName: 'Google',5 },6 })78 return (9 <FormProvider {...formMethods}>10 <MyFormWithContext />11 <MyForm />12 </FormProvider>13 )14}
- Child component
1const {2 handleSubmit,3 formState: { errors, isSubmitting, isValid },4 register,5 } = useFormContext()67 const Form () => {8 // components9 }
Explanation:
- We are using the
FormProvider
component to wrap the form components and spread the return object of theuseForm
hook. - We are using the
useFormContext
hook to get the form context in the child component. It will return the same object as theuseForm
hook. - The process is very similar to react context.
Learn more about form context from here.
To learn more about this, I would recommend checking my crash course.
Shameless Plug
I have made an Xbox landing page clone with React and Styled components. I hope you will enjoy it. Please consider like this video and subscribe to my channel.
That's it for this blog. I have tried to explain things simply. If you get stuck, you can ask me questions.
Contacts
- Email: thatanjan@gmail.com
- LinkedIn: @thatanjan
- Portfolio: anjan
- Github: @thatanjan
- Instagram : @thatanjan
- Twitter: @thatanjan
Blogs you might want to read:
- Eslint, prettier setup with TypeScript and react
- What is Client-Side Rendering?
- What is Server Side Rendering?
- Everything you need to know about tree data structure
- 13 reasons why you should use Nextjs
- Beginners guide to quantum computers
Videos might you might want to watch: